package top.yj95.codegenerator.util;

import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.InjectionConfig;
import com.baomidou.mybatisplus.generator.config.DataSourceConfig;
import com.baomidou.mybatisplus.generator.config.FileOutConfig;
import com.baomidou.mybatisplus.generator.config.GlobalConfig;
import com.baomidou.mybatisplus.generator.config.PackageConfig;
import com.baomidou.mybatisplus.generator.config.StrategyConfig;
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
import com.baomidou.mybatisplus.generator.config.rules.DateType;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;

import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.db.dialect.DriverUtil;

/**
 * MyBatis-Plus代码生成器
 * @author 杨军
 * @date 2022-04-10
 */
public class CodeGenerator {

	/** 当前项目根目录 */
	private static final String USER_DIR = System.getProperty("user.dir");

	/** 默认输出目录（当前用户主目录下的<code>CodeGenerator</code>目录） */
	private static final String DEFAULT_DIR = System.getProperty("user.home") + File.separator + "CodeGenerator";

	/** 保存源码的目录 */
	private static final String SOURCE_DIR = "src" + File.separator + "main" + File.separator + "java";

	/** 保存资源文件的目录 */
	private static final String RESOURCES_DIR = "src" + File.separator + "main" + File.separator + "resources";

	/**
	 * 创建默认配置构造器（数据库驱动会根据<code>url</code>自动识别）
	 * @param url 数据库连接地址
	 * @param username 用户名
	 * @param password 密码
	 * @param parent 父包名
	 * @param moduleName 父模块名
	 * @param include 要生成代码的表
	 * @return 默认的构造器
	 * @author 杨军
	 * @date 2022-04-10
	 */
	public static Builder custom(String url, String username, String password, String parent, String moduleName, String... include) {
		return new Builder().url(url).username(username).password(password).parent(parent).moduleName(moduleName).include(include);
	}

	/**
	 * 创建无需认证数据库的配置（比如：SQLite，数据库驱动会根据<code>url</code>自动识别）
	 * @param url 数据库连接地址
	 * @param parent 父包名
	 * @param moduleName 父模块名
	 * @param include 要生成代码的表
	 * @return 创建的配置构建器
	 * @author 杨军
	 * @date 2022-04-10
	 */
	public static Builder noAuthentication(String url, String parent, String moduleName, String... include) {
		return new Builder().url(url).parent(parent).moduleName(moduleName).include(include);
	}

	/**
	 * 配置构造器
	 * @author 杨军
	 * @date 2022-04-10
	 */
	public static class Builder {
		/* 全局配置 */
		/** 存放生成代码的根目录 */
		private String outputDir;
		/** 是否覆盖已有文件 */
		private boolean fileOverride;
		/** 是否打开输出目录 */
		private boolean open;
		/** 是否在xml中添加二级缓存配置 */
		private boolean enableCache;
		/** 开发人员 */
		private String author;
		/** 开启 Kotlin 模式 */
		private boolean kotlin;
		/** 开启 swagger2 模式 */
		private boolean swagger2;
		/** 开启 ActiveRecord 模式 */
		private boolean activeRecord;
		/** 开启 BaseResultMap */
		private boolean baseResultMap;
		/** 时间类型对应策略 */
		private DateType dateType;
		/** 开启 baseColumnList */
		private boolean baseColumnList;
		
		/* 各层文件名称方式，例如： %sAction 生成 UserAction，%s 为占位符 */
		/** 实体类占位符 */
		private String entityName;
		/** Mapper接口占位符 */
		private String mapperName;
		/** XML文件占位符 */
		private String xmlName;
		/** 服务接口占位符 */
		private String serviceName;
		/** 服务接口实现类占位符 */
		private String serviceImplName;
		/** 控制器占位符 */
		private String controllerName;
		/** 主键ID生成策略 */
		private IdType idType;

		/* 数据库配置 */
		/** 数据库连接地址 */
		private String url;
		/** 数据库驱动名称 */
		private String driverName;
		/** 数据库用户名 */
		private String username;
		/** 数据库密码 */
		private String password;
		/** 数据库模式名称 */
		private String schemaName;

		/* 策略配置 */
		/** 是否大写命名 */
		private boolean isCapitalMode;
		/** 是否跳过视图 */
		private boolean skipView;
		/** 数据库表映射到实体的命名策略 */
		private NamingStrategy naming;
		/** 数据库表字段映射到实体的命名策略（未指定按照<code>naming</code>执行） */
		private NamingStrategy columnNaming;
		/** 表前缀 */
		private String[] tablePrefix;
		/** 字段前缀 */
		private String[] fieldPrefix = {};
		/** 需要包含的表名，允许正则表达式（与exclude二选一配置）。当{@link #enableSqlFilter}为true时，正则表达式无效 */
		private String[] include;
		/** 需要排除的表名，允许正则表达式。当{@link #enableSqlFilter}为true时，正则表达式无效 */
		private String[] exclude = {};
		/** 实体是否为链式模型 */
		private boolean chainModel;
		/** 实体是否为lombok模型 */
		private boolean entityLombokModel;
		/** Boolean类型字段是否移除is前缀（默认 false），比如 : 数据库字段名称 : 'is_xxx',类型为 : tinyint. 在映射实体的时候则会去掉is,在实体类中映射最终结果为 xxx */
		private boolean entityBooleanColumnRemoveIsPrefix;
		/** 是否生成<code>RestController</code>控制器（默认<code>true</code>） */
		private boolean restControllerStyle;
		/** 控制器驼峰转连字符，比如：<code>@RequestMapping("/managerUserActionHistory")</code> -> <code>@RequestMapping("/manager-user-action-history")</code> */
		private boolean controllerMappingHyphenStyle;
		/** 生成实体类时是否为字段生成注解 */
		private boolean entityTableFieldAnnotationEnable;
		/** 乐观锁属性名称 */
		private String versionFieldName;
		/** 逻辑删除属性名称 */
		private String logicDeleteFieldName;
		/** 启用sql过滤，语法不能支持使用sql过滤表的话，可以考虑关闭此开关 */
		private boolean enableSqlFilter;

		/* 包相关配置 */
		/** 父包名。如果为空，将下面子包名必须写全部， 否则就只需写子包名 */
		private String parent;
		/** 父包模块名 */
		private String moduleName;
		/** 实体类包名 */
		private String entity;
		/** Service 包名 */
		private String service;
		/** Service Impl 包名 */
		private String serviceImpl;
		/** Mapper 包名 */
		private String mapper;
		/** Mapper XML 包名 */
		private String xml;
		/** Controller 包名 */
		private String controller;
		/** 是否将 Mapper XML 写入到 src/main/resources目录下 */
		private boolean writeXmlToResources;
		/** 是否将生成的代码写入到当前项目中 */
		private boolean writeCodeToCurrentProject;

		/**
		 * 根据配置构建代码生成器
		 * @return 构建好的代码生成器
		 * @author 杨军
		 * @date 2022-04-10
		 */
		public AutoGenerator build() {
			if ((null == include && null == exclude)
			    || (ArrayUtil.isEmpty(include = ArrayUtil.removeBlank(include)) && ArrayUtil.isEmpty(exclude = ArrayUtil.removeBlank(exclude)))) {
				throw new NullPointerException("未指定要生成代码的表");
			}
			AutoGenerator ag = new AutoGenerator();
			ag.setGlobalConfig(buildGlobalConfig());
			ag.setDataSource(buildDataSourceConfig());
			ag.setStrategy(buildStrategyConfig());
			PackageConfig pc = buildPackageConfig();
			ag.setPackageInfo(pc);
			ag.setCfg(buildInjectionConfig(pc));
			return ag;
		}

		private Builder() {
			this.outputDir = DEFAULT_DIR;
			this.fileOverride = false;
			this.open = false;
			this.author = System.getProperty("user.name"); // 默认取操作系统用户名
			this.kotlin = false;
			this.swagger2 = false;
			this.activeRecord = false;
			this.baseResultMap = false;
			this.dateType = DateType.TIME_PACK;
			this.baseColumnList = false;
			this.entityName = "%s";
			this.mapperName = "%sMapper";
			this.xmlName = "%sMapper";
			this.serviceName = "%sService";
			this.serviceImplName = "%sServiceImpl";
			this.controllerName = "%sController";
			this.idType = null;
			this.isCapitalMode = true;
			this.skipView = true;
			this.naming = NamingStrategy.underline_to_camel;
			this.columnNaming = NamingStrategy.underline_to_camel;
			this.chainModel = false;
			this.entityLombokModel = false;
			this.entityBooleanColumnRemoveIsPrefix = false;
			this.restControllerStyle = true;
			this.controllerMappingHyphenStyle = false;
			this.entityTableFieldAnnotationEnable = true;
			this.enableSqlFilter = false;
			this.entity = "entity";
			this.service = "service";
			this.serviceImpl = "service.impl";
			this.mapper = "mapper";
			this.xml = "mapper";
			this.controller = "controller";
			this.writeXmlToResources = false;
			this.writeCodeToCurrentProject = false;
		}

		public Builder outputDir(String outputDir) {
			this.outputDir = Optional.ofNullable(outputDir).orElse(DEFAULT_DIR);
			return this;
		}

		public String outputDir() {
			return outputDir;
		}

		public Builder fileOverride(boolean fileOverride) {
			this.fileOverride = fileOverride;
			return this;
		}

		public boolean fileOverride() {
			return fileOverride;
		}

		public Builder open(boolean open) {
			this.open = open;
			return this;
		}

		public boolean open() {
			return open;
		}

		public Builder enableCache(boolean enableCache) {
			this.enableCache = enableCache;
			return this;
		}

		public boolean enableCache() {
			return enableCache;
		}

		public Builder author(String author) {
			this.author = author;
			return this;
		}

		public String author() {
			return author;
		}

		public Builder kotlin(boolean kotlin) {
			this.kotlin = kotlin;
			return this;
		}

		public boolean kotlin() {
			return kotlin;
		}

		public Builder swagger2(boolean swagger2) {
			this.swagger2 = swagger2;
			return this;
		}

		public boolean swagger2() {
			return swagger2;
		}

		public Builder activeRecord(boolean activeRecord) {
			this.activeRecord = activeRecord;
			return this;
		}

		public boolean activeRecord() {
			return activeRecord;
		}

		public Builder baseResultMap(boolean baseResultMap) {
			this.baseResultMap = baseResultMap;
			return this;
		}

		public boolean baseResultMap() {
			return baseResultMap;
		}

		public Builder dateType(DateType dateType) {
			this.dateType = Optional.ofNullable(dateType).orElse(DateType.TIME_PACK);
			return this;
		}

		public DateType dateType() {
			return dateType;
		}

		public Builder baseColumnList(boolean baseColumnList) {
			this.baseColumnList = baseColumnList;
			return this;
		}

		public boolean baseColumnList() {
			return baseColumnList;
		}

		public Builder entityName(String entityName) {
			this.entityName = StrUtil.isBlank(entityName) ? "%s" : entityName;
			return this;
		}

		public String entityName() {
			return entityName;
		}

		public Builder mapperName(String mapperName) {
			this.mapperName = StrUtil.isBlank(mapperName) ? "%sMapper" : mapperName;
			return this;
		}

		public String mapperName() {
			return mapperName;
		}

		public Builder xmlName(String xmlName) {
			this.xmlName = StrUtil.isBlank(xmlName) ? "%sMapper" : xmlName;
			return this;
		}

		public String xmlName() {
			return xmlName;
		}

		public Builder serviceName(String serviceName) {
			this.serviceName = StrUtil.isBlank(serviceName) ? "%sService" : serviceName;
			return this;
		}

		public String serviceName() {
			return serviceName;
		}

		public Builder serviceImplName(String serviceImplName) {
			this.serviceImplName = StrUtil.isBlank(serviceImplName) ? "%sServiceImpl" : serviceImplName;
			return this;
		}

		public String serviceImplName() {
			return serviceImplName;
		}

		public Builder controllerName(String controllerName) {
			this.controllerName = StrUtil.isBlank(controllerName) ? "%sController" : controllerName;
			return this;
		}

		public String controllerName() {
			return controllerName;
		}

		public Builder idType(IdType idType) {
			this.idType = idType;
			return this;
		}

		public IdType idType() {
			return idType;
		}

		public Builder url(String url) {
			Objects.requireNonNull(url, "数据库连接地址不能为空");
			this.url = url;
			this.driverName = DriverUtil.identifyDriver(url); // 通过JDBC URL识别JDBC驱动名称
			return this;
		}

		public String url() {
			return url;
		}

		public Builder driverName(String driverName) {
			this.driverName = driverName;
			return this;
		}

		public String driverName() {
			return driverName;
		}

		public Builder username(String username) {
			this.username = username;
			return this;
		}

		public String username() {
			return username;
		}

		public Builder password(String password) {
			this.password = password;
			return this;
		}

		public String password() {
			return password;
		}

		public Builder schemaName(String schemaName) {
			this.schemaName = schemaName;
			return this;
		}

		public String schemaName() {
			return schemaName;
		}

		public Builder isCapitalMode(boolean isCapitalMode) {
			this.isCapitalMode = isCapitalMode;
			return this;
		}

		public boolean isCapitalMode() {
			return isCapitalMode;
		}

		public Builder skipView(boolean skipView) {
			this.skipView = skipView;
			return this;
		}

		public boolean skipView() {
			return skipView;
		}

		public Builder naming(NamingStrategy naming) {
			this.naming = Optional.ofNullable(naming).orElse(NamingStrategy.underline_to_camel);
			return this;
		}

		public NamingStrategy naming() {
			return naming;
		}

		public Builder columnNaming(NamingStrategy columnNaming) {
			this.columnNaming = Optional.ofNullable(Optional.ofNullable(columnNaming).orElse(naming())).orElse(this.naming(NamingStrategy.underline_to_camel).naming());
			return this;
		}

		public NamingStrategy columnNaming() {
			return columnNaming;
		}

		public Builder tablePrefix(String[] tablePrefix) {
			this.tablePrefix = tablePrefix;
			return this;
		}

		public String[] tablePrefix() {
			return tablePrefix;
		}

		public Builder fieldPrefix(String[] fieldPrefix) {
			this.fieldPrefix = fieldPrefix;
			return this;
		}

		public String[] fieldPrefix() {
			return fieldPrefix;
		}

		public Builder include(String[] include) {
			this.include = include;
			return this;
		}

		public String[] include() {
			return include;
		}

		public Builder exclude(String[] exclude) {
			this.exclude = exclude;
			return this;
		}

		public String[] exclude() {
			return exclude;
		}

		public Builder chainModel(boolean chainModel) {
			this.chainModel = chainModel;
			return this;
		}

		public boolean chainModel() {
			return chainModel;
		}

		public Builder entityLombokModel(boolean entityLombokModel) {
			this.entityLombokModel = entityLombokModel;
			return this;
		}

		public boolean entityLombokModel() {
			return entityLombokModel;
		}

		public Builder entityBooleanColumnRemoveIsPrefix(boolean entityBooleanColumnRemoveIsPrefix) {
			this.entityBooleanColumnRemoveIsPrefix = entityBooleanColumnRemoveIsPrefix;
			return this;
		}

		public boolean entityBooleanColumnRemoveIsPrefix() {
			return entityBooleanColumnRemoveIsPrefix;
		}

		public Builder restControllerStyle(boolean restControllerStyle) {
			this.restControllerStyle = restControllerStyle;
			return this;
		}

		public boolean restControllerStyle() {
			return restControllerStyle;
		}

		public Builder controllerMappingHyphenStyle(boolean controllerMappingHyphenStyle) {
			this.controllerMappingHyphenStyle = controllerMappingHyphenStyle;
			return this;
		}

		public boolean controllerMappingHyphenStyle() {
			return controllerMappingHyphenStyle;
		}

		public Builder entityTableFieldAnnotationEnable(boolean entityTableFieldAnnotationEnable) {
			this.entityTableFieldAnnotationEnable = entityTableFieldAnnotationEnable;
			return this;
		}

		public boolean entityTableFieldAnnotationEnable() {
			return entityTableFieldAnnotationEnable;
		}

		public Builder versionFieldName(String versionFieldName) {
			this.versionFieldName = versionFieldName;
			return this;
		}

		public String versionFieldName() {
			return versionFieldName;
		}

		public Builder logicDeleteFieldName(String logicDeleteFieldName) {
			this.logicDeleteFieldName = logicDeleteFieldName;
			return this;
		}

		public String logicDeleteFieldName() {
			return logicDeleteFieldName;
		}

		public Builder enableSqlFilter(boolean enableSqlFilter) {
			this.enableSqlFilter = enableSqlFilter;
			return this;
		}

		public boolean enableSqlFilter() {
			return enableSqlFilter;
		}

		public Builder parent(String parent) {
			this.parent = parent;
			return this;
		}

		public String parent() {
			return parent;
		}

		public Builder moduleName(String moduleName) {
			this.moduleName = moduleName;
			return this;
		}

		public String moduleName() {
			return moduleName;
		}

		public Builder entity(String entity) {
			this.entity = entity;
			return this;
		}

		public String entity() {
			return entity;
		}

		public Builder service(String service) {
			this.service = service;
			return this;
		}

		public String service() {
			return service;
		}

		public Builder serviceImpl(String serviceImpl) {
			this.serviceImpl = serviceImpl;
			return this;
		}

		public String serviceImpl() {
			return serviceImpl;
		}

		public Builder mapper(String mapper) {
			this.mapper = mapper;
			return this;
		}

		public String mapper() {
			return mapper;
		}

		public Builder xml(String xml) {
			this.xml = xml;
			return this;
		}

		public String xml() {
			return xml;
		}

		public Builder controller(String controller) {
			this.controller = controller;
			return this;
		}

		public String controller() {
			return controller;
		}

		public Builder writeXmlToResources(boolean writeXmlToResources) {
			this.writeXmlToResources = writeXmlToResources;
			return this;
		}

		public boolean writeXmlToResources() {
			return writeXmlToResources;
		}

		public Builder writeCodeToCurrentProject(boolean writeCodeToCurrentProject) {
			this.writeCodeToCurrentProject = writeCodeToCurrentProject;
			if (writeCodeToCurrentProject) {
				this.outputDir = USER_DIR;
			}
			return this;
		}

		public boolean writeCodeToCurrentProject() {
			return writeCodeToCurrentProject;
		}

		/**
		 * 构建全局配置
		 * @return 全局配置
		 * @author 杨军
		 * @date 2022-04-10
		 */
		private GlobalConfig buildGlobalConfig() {
			GlobalConfig gc = new GlobalConfig();
			outputDir = null == outputDir ? DEFAULT_DIR : outputDir;
			if (writeCodeToCurrentProject) {
				outputDir = USER_DIR;
			}
			gc.setOutputDir(outputDir + File.separator + SOURCE_DIR);
			gc.setFileOverride(fileOverride);
			gc.setOpen(open);
			gc.setEnableCache(enableCache);
			gc.setAuthor(author);
			gc.setKotlin(kotlin);
			gc.setSwagger2(swagger2);
			gc.setActiveRecord(activeRecord);
			gc.setBaseResultMap(baseResultMap);
			gc.setDateType(dateType);
			gc.setBaseColumnList(baseColumnList);
			gc.setEntityName(entityName);
			gc.setMapperName(mapperName);
			gc.setXmlName(xmlName);
			gc.setServiceName(serviceName);
			gc.setServiceImplName(serviceImplName);
			gc.setControllerName(controllerName);
			gc.setIdType(idType);
			return gc;
		}

		/**
		 * 构建数据源配置
		 * @return 构建好的数据源配置
		 * @author 杨军
		 * @date 2022-04-10
		 */
		private DataSourceConfig buildDataSourceConfig() {
			DataSourceConfig dsc = new DataSourceConfig();
			dsc.setUrl(url);
			dsc.setDriverName(driverName);
			dsc.setUsername(username);
			dsc.setPassword(password);
			dsc.setSchemaName(schemaName);
			return dsc;
		}

		/**
		 * 构建代码生成策略配置
		 * @return 构建好的代码生成策略配置
		 * @author 杨军
		 * @date 2022-04-10
		 */
		private StrategyConfig buildStrategyConfig() {
			StrategyConfig sc = new StrategyConfig();
			sc.setCapitalMode(isCapitalMode);
			sc.setSkipView(skipView);
			sc.setNaming(naming);
			sc.setColumnNaming(columnNaming);
			sc.setTablePrefix(tablePrefix);
			sc.setFieldPrefix(fieldPrefix);
			sc.setInclude(include);
			sc.setExclude(exclude);
			sc.setChainModel(chainModel);
			sc.setEntityLombokModel(entityLombokModel);
			sc.setEntityBooleanColumnRemoveIsPrefix(entityBooleanColumnRemoveIsPrefix);
			sc.setRestControllerStyle(restControllerStyle);
			sc.setControllerMappingHyphenStyle(controllerMappingHyphenStyle);
			sc.setEntityTableFieldAnnotationEnable(entityTableFieldAnnotationEnable);
			sc.setVersionFieldName(versionFieldName);
			sc.setLogicDeleteFieldName(logicDeleteFieldName);
			sc.setEnableSqlFilter(enableSqlFilter);
			return sc;
		}

		/**
		 * 构建包配置
		 * @return 构建好的包配置
		 * @author 杨军
		 * @date 2022-04-10
		 */
		private PackageConfig buildPackageConfig() {
			PackageConfig pc = new PackageConfig();
			pc.setParent(parent);
			pc.setModuleName(moduleName);
			pc.setEntity(entity);
			pc.setService(service);
			pc.setServiceImpl(serviceImpl);
			pc.setMapper(mapper);
			pc.setXml(xml + "/xml");
			pc.setController(controller);
			return pc;
		}

		/**
		 * 构建自定义配置
		 * @param pc 包配置
		 * @return 如果<code>writeXmlToResources</code>为<code>true</code>，返回构建好的自定义配置，否则返回<code>null</code>
		 * @author 杨军
		 * @date 2022-04-10
		 */
		private InjectionConfig buildInjectionConfig(PackageConfig pc) {
			InjectionConfig ic = null;
			if (writeXmlToResources) {
				ic = new InjectionConfig() {
					@Override
					public void initMap() {
					}
				};
				// 自定义 Mapper XML 文件输出路径
				final String templatePath = "/templates/mapper.xml.vm"; // 模板引擎是 velocity
				List<FileOutConfig> focList = new ArrayList<>(4); // 自定义输出配置
				focList.add(new FileOutConfig(templatePath) {
					@Override
					public String outputFile(TableInfo tableInfo) {
						return outputDir + File.separator + RESOURCES_DIR + File.separator + pc.getXml() + File.separator + tableInfo.getEntityName() + "Mapper"
						    + StringPool.DOT_XML;
					}
				});
				ic.setFileOutConfigList(focList);
			}
			return ic;
		}

	}

	private CodeGenerator() {}

}
